package com.elitedatai.mchtest.manage.service.impl;

import com.dji.sdk.cloudapi.device.DeviceDomainEnum;
import com.dji.sdk.cloudapi.hms.*;
import com.dji.sdk.cloudapi.hms.api.AbstractHmsService;
import com.dji.sdk.common.Pagination;
import com.dji.sdk.common.PaginationData;
import com.dji.sdk.mqtt.events.TopicEventsRequest;
import com.elitedatai.mchtest.component.websocket.model.BizCodeEnum;
import com.elitedatai.mchtest.component.websocket.service.IWebSocketMessageService;
import com.elitedatai.mchtest.manage.model.common.HmsJsonUtil;
import com.elitedatai.mchtest.manage.model.common.HmsMessage;
import com.elitedatai.mchtest.manage.model.dto.DeviceDTO;
import com.elitedatai.mchtest.manage.model.dto.DeviceHmsDTO;
import com.elitedatai.mchtest.manage.model.dto.TelemetryDTO;
import com.elitedatai.mchtest.manage.model.entity.DeviceHmsEntity;
import com.elitedatai.mchtest.manage.model.enums.UserTypeEnum;
import com.elitedatai.mchtest.manage.model.param.DeviceHmsQueryParam;
import com.elitedatai.mchtest.manage.repository.DeviceHmsRepository;
import com.elitedatai.mchtest.manage.service.IDeviceHmsService;
import com.elitedatai.mchtest.manage.service.IDeviceRedisService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Pageable;



@Service
@Transactional
@Slf4j
public class DeviceHmsServiceImpl extends AbstractHmsService implements IDeviceHmsService {

    @Autowired
    private DeviceHmsRepository repository;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private IWebSocketMessageService sendMessageService;

    @Autowired
    private IDeviceRedisService deviceRedisService;

    private static final Pattern PATTERN_KEY = Pattern.compile(
                    "(" +
                    Arrays.stream(HmsFormatKeyEnum.values())
                            .map(HmsFormatKeyEnum::getKey)
                            .collect(Collectors.joining("|")) +
                    ")");

    /**
     * 处理设备健康管理系统(HMS)消息
     * @param response 包含HMS消息的请求对象
     * @param headers 消息头
     */
    @Override
    public void hms(TopicEventsRequest<Hms> response, MessageHeaders headers) {
        // 从响应中获取设备序列号
        String sn = response.getFrom();

        // 构建设备HMS实体对象，初始化基础信息
        DeviceHmsEntity entity = DeviceHmsEntity.builder()
                .bid(response.getBid())           // 业务ID
                .tid(response.getTid())           // 主题ID
                .createTime(response.getTimestamp()) // 创建时间
                .updateTime(0L)                   // 更新时间初始化为0，表示未读
                .sn(sn)                           // 设备序列号
                .build();

        // 从Redis中查询该设备所有未读HMS消息的键，用于后续去重判断
        // Redis键名格式为: hms:{sn}
        Set<String> hmsMap = deviceRedisService.getAllHmsKeys(sn);

        // 存储新接收到的未读HMS消息列表
        List<DeviceHmsDTO> unReadList = new ArrayList<>();

        // 遍历接收到的HMS消息列表
        response.getData().getList()
                .forEach(hmsReceiver -> {
                    // 克隆基础实体对象，为每条消息创建独立的实体
                    final DeviceHmsEntity hms = entity.clone();

                    // 填充实体字段，包括设备类型、级别、模块等信息
                    // 键的生成规则：
                    // 1. 机库设备：dock_tip_ + code
                    // 2. 飞行器设备：fpv_tip_ + code + (inTheSky ? "_in_the_sky" : "")
                    this.fillEntity(hms, hmsReceiver);

                    // 检查消息是否已存在（通过hmsKey判断），避免重复处理
                    // 如果Redis中已存在相同的键，则跳过该消息
                    if (hmsMap.contains(hms.getHmsKey())) {
                        return;
                    }

                    // 填充消息内容，从hms.json文件中获取中英文消息模板，并替换占位符
                    // 占位符包括：%alarmid, %component_index, %index等
                    this.fillMessage(hms, hmsReceiver.getArgs());

                    // 将实体转换为DTO并添加到未读列表
                    unReadList.add(entity2Dto(hms));

                    // 将新消息插入数据库
                    repository.save(hms);
                });

        // 如果没有新的未读消息，则直接返回
        if (unReadList.isEmpty()) {
            return;
        }

        // 将新消息的键添加到Redis缓存中
        deviceRedisService.addEndHmsKeys(sn, unReadList.stream().map(DeviceHmsDTO::getKey).toArray(String[]::new));

        // 推送消息到Web端
        // 1. 从Redis中获取设备信息
        Optional<DeviceDTO> deviceOpt = deviceRedisService.getDeviceOnline(sn);
        if (deviceOpt.isEmpty()) {
            return;
        }

        // 2. 通过WebSocket批量发送HMS消息给Web端用户
        sendMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), UserTypeEnum.WEB.getVal(),
                BizCodeEnum.DEVICE_HMS.getCode(), TelemetryDTO.<List<DeviceHmsDTO>>builder().sn(sn).host(unReadList).build());
    }

    /**
     * 根据参数查询设备HMS信息
     * @param param 查询参数
     * @return 分页的设备HMS信息
     */
    @Override
    public PaginationData<DeviceHmsDTO> getDeviceHmsByParam(DeviceHmsQueryParam param) {
        // 1. 处理分页：JPA 页码从 0 开始
        int page = (param.getPage() == null || param.getPage() <= 0) ? 0 : param.getPage().intValue() - 1;
        int pageSize = (param.getPageSize() == null) ? 10 : param.getPageSize().intValue();

        // 特殊处理：如果 page 或 pageSize 为空，查全部
        if (param.getPage() == null || param.getPageSize() == null) {
            pageSize = Integer.MAX_VALUE; // 查所有
            page = 0;
        }

        Pageable pageable = PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC, "createTime"));

        // 2. 提取参数
        Set<String> snList = param.getDeviceSn();
        Long beginTime = param.getBeginTime();
        Long endTime = param.getEndTime();
        Long updateTime = param.getUpdateTime();
        Integer level = param.getLevel();
        String language = param.getLanguage(); // 'zh' or 'en'
        String message = param.getMessage();

        // 3. 执行查询
        Page<DeviceHmsEntity> pageResult = repository.findWithFilters(
                snList,
                beginTime,
                endTime,
                updateTime,
                level,
                language,
                message,
                pageable
        );

        // 4. 转换为 DTO 列表
        List<DeviceHmsDTO> dtoList = pageResult.getContent().stream()
                .map(this::entity2Dto)
                .collect(Collectors.toList());

        Pagination pagination = new Pagination(
                pageResult.getNumber() + 1,
                pageResult.getSize(),
                pageResult.getTotalElements()
        );

        return new PaginationData<>(dtoList, pagination);
    }

    /**
     * 更新未读HMS消息状态
     * @param deviceSn 设备序列号
     */
    @Override
    public void updateUnreadHms(String deviceSn) {
        DeviceHmsEntity entity = repository.findByDeviceSnAnd0UpdateTime(deviceSn);
        if (entity == null) {
            return;
        }
        entity.setUpdateTime(System.currentTimeMillis());
        repository.save(entity);
        // Delete unread messages cached in redis.
        deviceRedisService.delHmsKeysBySn(deviceSn);
    }

    /**
     * 将设备HMS实体转换为DTO
     * @param entity 设备HMS实体
     * @return 设备HMS DTO
     */
    private DeviceHmsDTO entity2Dto(DeviceHmsEntity entity) {
        if (entity == null) {
            return null;
        }
        return DeviceHmsDTO.builder()
                .bid(entity.getBid())
                .tid(entity.getTid())
                .createTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getCreateTime()), ZoneId.systemDefault()))
                .updateTime(entity.getUpdateTime().intValue() == 0 ?
                        null : LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault()))
                .sn(entity.getSn())
                .hmsId(entity.getHmsId())
                .key(entity.getHmsKey())
                .level(entity.getLevel())
                .module(entity.getModule())
                .messageEn(entity.getMessageEn())
                .messageZh(entity.getMessageZh())
                .build();
    }

    /**
     * 填充接收到的数据到实体中。请参考文档了解拼接规则。
     * @param dto 设备HMS实体
     * @param receiver HMS接收数据
     */
    private void fillEntity(DeviceHmsEntity dto, DeviceHms receiver) {
        dto.setLevel(receiver.getLevel().getLevel());
        dto.setModule(receiver.getModule().getModule());
        dto.setHmsId(UUID.randomUUID().toString());
        DeviceDomainEnum domain = receiver.getDeviceType().getDomain();
        if (DeviceDomainEnum.DOCK == domain) {
            dto.setHmsKey(HmsFaqIdEnum.DOCK_TIP.getText() + receiver.getCode());
            return;
        }
        StringBuilder key = new StringBuilder(HmsFaqIdEnum.FPV_TIP.getText()).append(receiver.getCode());
        if (receiver.getInTheSky()) {
            key.append(HmsInTheSkyEnum.IN_THE_SKY.getText());
        }
        dto.setHmsKey(key.toString());
    }

    /**
     * 根据相关规则替换消息中的通配符。
     * 请参考文档了解拼接规则。
     * @param dto 设备HMS实体
     * @param args HMS参数
     */
    private void fillMessage(DeviceHmsEntity dto, DeviceHmsArgs args) {
        HmsMessage hmsMessage = HmsJsonUtil.get(dto.getHmsKey());
        String zh = StringUtils.hasText(hmsMessage.getZh()) ? hmsMessage.getZh() : String.format("未知错误（%s）", dto.getHmsKey());
        String en = StringUtils.hasText(hmsMessage.getEn()) ? hmsMessage.getEn() : String.format("Unknown(%s)", dto.getHmsKey());//
        dto.setMessageZh(format(Locale.CHINESE.getLanguage(), zh, args));
        dto.setMessageEn(format(Locale.ENGLISH.getLanguage(), en, args));
    }

    /**
     * 设置键的匹配参数。
     * @param l 语言: zh 或 en
     * @param hmsArgs HMS参数
     * @return 参数映射
     */
    private Map<String, String> fillKeyArgs(String l, DeviceHmsArgs hmsArgs) {
        Map<String, String> args = new HashMap<>();
        args.put(HmsFormatKeyEnum.ALARM_ID.getKey(), Objects.nonNull(hmsArgs.getAlarmId()) ? Long.toHexString(hmsArgs.getAlarmId()) : null);
        args.put(HmsFormatKeyEnum.COMPONENT_INDEX.getKey(),
                Objects.nonNull(hmsArgs.getComponentIndex()) ? String.valueOf(hmsArgs.getComponentIndex() + 1) : null);
        if (Objects.nonNull(hmsArgs.getSensorIndex())) {
            args.put(HmsFormatKeyEnum.INDEX.getKey(), String.valueOf(hmsArgs.getSensorIndex() + 1));

            HmsBatteryIndexEnum hmsBatteryIndexEnum = Optional.ofNullable(hmsArgs.getSensorIndex())
                    .filter(arg -> arg <= 1).map(HmsBatteryIndexEnum::find).orElse(null);
            HmsDockCoverIndexEnum hmsDockCoverIndexEnum = Optional.ofNullable(hmsArgs.getSensorIndex())
                    .filter(arg -> arg <= 1).map(HmsDockCoverIndexEnum::find).orElse(null);
            HmsChargingRodIndexEnum hmsChargingRodIndexEnum = Optional.ofNullable(hmsArgs.getSensorIndex())
                    .filter(arg -> arg <= 3).map(HmsChargingRodIndexEnum::find).orElse(null);

            switch (l) {
                case "zh":
                    args.put(HmsFormatKeyEnum.BATTERY_INDEX.getKey(), Optional.ofNullable(hmsBatteryIndexEnum)
                            .map(HmsBatteryIndexEnum::getZh).orElse(null));
                    args.put(HmsFormatKeyEnum.DOCK_COVER_INDEX.getKey(), Optional.ofNullable(hmsDockCoverIndexEnum)
                            .map(HmsDockCoverIndexEnum::getZh).orElse(null));
                    args.put(HmsFormatKeyEnum.CHARGING_ROD_INDEX.getKey(), Optional.ofNullable(hmsChargingRodIndexEnum)
                            .map(HmsChargingRodIndexEnum::getZh).orElse(null));
                    break;
                case "en":
                    args.put(HmsFormatKeyEnum.BATTERY_INDEX.getKey(), Optional.ofNullable(hmsBatteryIndexEnum)
                            .map(HmsBatteryIndexEnum::getEn).orElse(null));
                    args.put(HmsFormatKeyEnum.DOCK_COVER_INDEX.getKey(), Optional.ofNullable(hmsDockCoverIndexEnum)
                            .map(HmsDockCoverIndexEnum::getEn).orElse(null));
                    args.put(HmsFormatKeyEnum.CHARGING_ROD_INDEX.getKey(), Optional.ofNullable(hmsChargingRodIndexEnum)
                            .map(HmsChargingRodIndexEnum::getEn).orElse(null));
                    break;
                default:
                    break;
            }

        }
        return args;
    }

    /**
     * 使用指定的语言、格式字符串和参数返回格式化的字符串。
     * @param l 语言: zh 或 en
     * @param format 格式字符串
     * @param hmsArgs HMS参数
     * @return 格式化后的字符串
     */
    private String format(String l, String format, DeviceHmsArgs hmsArgs) {
        Map<String, String> args = fillKeyArgs(l, hmsArgs);
        List<String> list = parse(format);
        StringBuilder sb = new StringBuilder();
        for (String word : list) {
            if (!StringUtils.hasText(word)) {
                continue;
            }
            sb.append(args.getOrDefault(word, word));
        }
        return sb.toString();
    }

    /**
     * 在格式字符串中查找格式说明符。
     * @param s 字符串
     * @return 格式说明符列表
     */
    private List<String> parse(String s) {
        List<String> list = new ArrayList<>();
        Matcher matcher = PATTERN_KEY.matcher(s);
        for (int i = 0; i < s.length(); ) {
            if (matcher.find(i)) {
                if (matcher.start() != i) {
                    list.add(s.substring(i, matcher.start()));
                }
                list.add(matcher.group());
                i = matcher.end();
            } else {
                list.add(s.substring(i));
                break;
            }
        }
        return list;
    }
}